// Copyright 2003-2023 by Autodesk, Inc.
// 
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
// 
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
// 
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.


using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Runtime.ExceptionServices;

#if Gdmp || Gnuf
using Glodon.Gdmp.DB;
#elif Gap
using Glodon.Gap.DB;
#endif
using Glodon.Lookup.Core.Contracts;
using Glodon.Lookup.Core.Enums;
using Glodon.Lookup.Core.Extensions;
using Glodon.Lookup.Core.ModelBase;
using Glodon.Lookup.Lookup.Core;
using Glodon.Lookup.Models;
using Glodon.Lookup.Models.Descriptors;
using Glodon.Lookup.Services.Contracts;

using JetBrains.Annotations;

namespace Glodon.Lookup.Core.Utils
{
    public sealed class DescriptorBuilder
    {
        private readonly List<Descriptor> _descriptors;
        private readonly ISettingsService _settings;
        private readonly SnoopableObject _snoopableObject;
        [CanBeNull] private Descriptor? _currentDescriptor;
        private Type? _type;
        private readonly ReadOnlyCollection<string> _operationMethodNames;
        public DescriptorBuilder(SnoopableObject snoopableObject)
        {
            _snoopableObject = snoopableObject;
            _descriptors = new List<Descriptor>(16);
            _settings = Host.GetService<ISettingsService>();
            _operationMethodNames = new ReadOnlyCollection<string>(new List<string>()
            {
                "Reverse"
            });
        }

        public IReadOnlyCollection<Descriptor> Build()
        {
            if (_snoopableObject.Object is null)
            {
                return Array.Empty<Descriptor>();
            }
            else if (_snoopableObject.Object.GetType().Name == nameof(Type))
            {
                return BuildStaticObject((_snoopableObject.Object as Type)!);
            }
            else
            {
                return BuildInstanceObject(_snoopableObject.Object.GetType());
            }
        }

        private IReadOnlyCollection<Descriptor> BuildInstanceObject(Type type)
        {
            var types = new List<Type>();
            while (type.BaseType is not null)
            {
                types.Add(type);
                type = type.BaseType;
            }

            for (var i = types.Count - 1; i >= 0; i--)
            {
                _type = types[i];

                //Finding a descriptor to analyze IDescriptorResolver and IDescriptorExtension interfaces
                _currentDescriptor = DescriptorUtils.FindSuitableDescriptor(_snoopableObject.Object, _type);

                AddProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                AddMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                AddExtensions();
            }

            AddEnumerableItems();
            return _descriptors;
        }

        private IReadOnlyCollection<Descriptor> BuildStaticObject(Type staticObjectType)
        {
            _type = staticObjectType;
            _snoopableObject.Object = null!;
            AddProperties(BindingFlags.Public | BindingFlags.Static);
            AddMethods(BindingFlags.Public | BindingFlags.Static);
            return _descriptors;
        }

        private void AddProperties(BindingFlags bindingFlags)
        {
            var members = _type!.GetProperties(bindingFlags);
            var descriptors = new List<Descriptor>(members.Length);

            foreach (var member in members)
            {
                if (member.IsSpecialName) continue;

                object value;
                ParameterInfo[]? parameters = null;
                try
                {
                    if (!TryEvaluate(member, out value, out parameters)) continue;
                }
                catch (Exception exception)
                {
                    value = exception;
                }

                var descriptor = CreateDescriptor(member, value, parameters!);
                descriptors.Add(descriptor);
            }

            ApplyGroupCollector(descriptors);
        }
        [HandleProcessCorruptedStateExceptions]
        private void AddMethods(BindingFlags bindingFlags)
        {
            var members = _type!.GetMethods(bindingFlags);
            var descriptors = new List<Descriptor>(members.Length);

            foreach (var member in members)
            {
                if (member.IsSpecialName) continue;
                if (!member.Name.StartsWith("Get") && _currentDescriptor is not IDescriptorResolver) continue;

                object value;
                ParameterInfo[]? parameters = null;
                try
                {
                    if (_snoopableObject.Object is Document && member.Name.Equals("Save", StringComparison.Ordinal))
                    {
                        continue;
                    }
                    if (member.Name.Equals("GetGeometry", StringComparison.Ordinal))
                    {
                        List<GeometryOptions> geometryOptions = new List<GeometryOptions>()
                        {
                            new() {
                                ModelView = GlodonAPI.ActiveView,
                                ComputeReferences = true,
                            },
                            new () {
                                ModelView = GlodonAPI.ActiveView,
                                ComputeReferences = false,
                            },
                            new (){
                                ComputeReferences = true
                            },
                            new (){
                                ComputeReferences = false
                            }
                        };
                        foreach (var option in geometryOptions)
                        {
                            if (!TryEvaluate(member, out value, out parameters, option))
                            {
                                continue;
                            }
                            string modelViewDesc = option.ModelView is null ? "no spec view" : $"{option.ModelView.Name}";
                            string computeReferencesDesc = $"ComputeReferences:{option.ComputeReferences}";
                            var geomDescriptor = CreateDescriptor(member, value, parameters!, $"\r\n{{{modelViewDesc}, {computeReferencesDesc}}}");
                            descriptors.Add(geomDescriptor);
                        }
                        continue;
                    }
                    else
                    {
                        if (!TryEvaluate(member, out value, out parameters))
                        {
                            continue;
                        }
                    }
                }
                catch (Exception exception)
                {
                    value = exception;
                }

                var descriptor = CreateDescriptor(member, value, parameters!);
                descriptors.Add(descriptor);
            }

            ApplyGroupCollector(descriptors);
        }
        private void WriteDescriptor(string name, object value)
        {
            var descriptor = new ObjectDescriptor
            {
                Name = name,
                Value = EvaluateValue(value),
                TypeFullName = _type!.FullName,
                MemberAttributes = MemberAttributes.Extension,
                Type = DescriptorUtils.MakeGenericTypeName(_type)
            };

            _descriptors.Add(descriptor);
        }
        private void AddExtensions()
        {
            if (_currentDescriptor is not IDescriptorExtension extension) return;

            var manager = new ExtensionManager(_snoopableObject.Context!);
            extension.RegisterExtensions(manager);

            foreach (var pair in manager.ValuesMap)
            {
                WriteDescriptor(pair.Key, pair.Value);
            }

        }
        private void AddEnumerableItems()
        {
            if (_snoopableObject.Object is not IEnumerable enumerable) return;

            var enumerator = enumerable.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var enumerableDescriptor = new ObjectDescriptor
                {
                    Type = nameof(IEnumerable),
                    Value = new SnoopableObject(_snoopableObject.Context!, enumerator)
                };

                SnoopUtils.Redirect(enumerableDescriptor.Value);
                enumerableDescriptor.Name = enumerableDescriptor.Value.Descriptor!.Type;
                _descriptors.Add(enumerableDescriptor);
            }
        }
        [HandleProcessCorruptedStateExceptions]
        private bool TryEvaluate(PropertyInfo member, out object value, out ParameterInfo[] parameters)
        {
            value = null!;
            parameters = null!;
            try
            {
                if (!member.CanRead)
                {
                    value = new NotSupportedException("属性没有getter，不可读");
                    return true;
                }

                parameters = member.GetMethod.GetParameters();
                if (_currentDescriptor is IDescriptorResolver resolver)
                {
                    value = resolver.Resolve(_snoopableObject.Context!, member.Name, parameters)!;
                    if (!(value is null)) return true;
                }

                if (parameters.Length > 0)
                {
                    if (!_settings.IsUnsupportedAllowed) return false;

                    value = new NotSupportedException("不支持有参数的重载属性");
                    return true;
                }

                if (member.PropertyType.FullName.Equals("System.Guid"))
                {
                    var guid = (Guid)member.GetValue(_snoopableObject.Object);
                    if (guid.Equals(Guid.Empty))
                    {
                        value = null!;
                        return true;
                    }
                    else
                    {
                        value = guid;
                        return true;
                    }
                }
                else
                {
                    value = member.GetValue(_snoopableObject.Object);
                }
                return true;
            }
            catch (Exception e)
            {
                value = e;
                return false;
            }
        }
        [HandleProcessCorruptedStateExceptions]
        private bool TryEvaluate(MethodInfo method, out object value, out ParameterInfo[] parameters, params object[] parameterObjs)
        {
            value = null!;
            parameters = method.GetParameters();
            try
            {
                if (method.ReturnType.Name == "Void")
                {
                    if (!_settings.IsUnsupportedAllowed) return false;

                    value = new NotSupportedException("方法没有返回值");
                    return true;
                }

                if (_currentDescriptor is IDescriptorResolver resolver)
                {
                    if (method.Name.Equals("GetGeometry", StringComparison.Ordinal))
                    {
                        var geometryOptions = parameterObjs[0] as GeometryOptions;
                        if (null == geometryOptions)
                        {
                            value = method.Invoke(_snoopableObject.Object, parameterObjs);
                            return true;
                        }
                        value = resolver.Resolve(_snoopableObject.Context!, method.Name, parameterObjs)!;
                        return true;
                    }
                    else
                    {
                        value = resolver.Resolve(_snoopableObject.Context!, method.Name, parameters)!;
                        if (value is not null) return true;
                    }
                }

                if (parameters.Length > 0)
                {
                    if (!_settings.IsUnsupportedAllowed) return false;
                    value = new NotSupportedException("不支持有参数的重载方法");
                    return true;
                }
                if (_operationMethodNames.Contains(method.Name))
                {
                    value = "未反射";
                }
                else
                {
                    value = method.Invoke(_snoopableObject.Object, null);
                }
                return true;
            }
            catch (Exception e)
            {
                value = e;
                return false;
            }

        }
        private void ApplyGroupCollector(List<Descriptor> descriptors)
        {
            descriptors.Sort();
            _descriptors.AddRange(descriptors);
        }

        private ObjectDescriptor CreateDescriptor(MemberInfo member, object value, ParameterInfo[] parameters, string parameterDesc = "")
        {
            var descriptor = new ObjectDescriptor
            {
                TypeFullName = member.ReflectedType!.FullName,
                Type = DescriptorUtils.MakeGenericTypeName(_type!),
                Name = EvaluateName(member, parameters, parameterDesc),
                Value = EvaluateValue(member, value),
                MemberAttributes = EvaluateAttributes(member)
            };
            return descriptor;
        }

        private SnoopableObject EvaluateValue(MemberInfo member, object value)
        {
            var isVariants = value is ResolveSet set && set.Variants.Count != 1;
            var emptyVariants = value is ResolveSet set2 && set2.Variants.Count == 0;
            var snoopableObject = new SnoopableObject(_snoopableObject.Context!, value);

            SnoopUtils.Redirect(member.Name, snoopableObject);

            if (isVariants && !emptyVariants)
            {
                if (member.GetType().Name == nameof(PropertyInfo))
                {
                    snoopableObject.Descriptor!.Name = DescriptorUtils.MakeGenericTypeName((member as PropertyInfo)!.GetMethod.ReturnType);
                }
                else if (member.GetType().Name == nameof(MethodInfo))
                {
                    snoopableObject.Descriptor!.Name = DescriptorUtils.MakeGenericTypeName((member as MethodInfo)!.ReturnType);
                }

                // snoopableObject.Descriptor.TypeFullName = $"{member.ReflectedType!.FullName}";
                snoopableObject.Descriptor!.Type = snoopableObject.Descriptor.Name;
            }
            else if (isVariants && emptyVariants)
            {
                snoopableObject.Descriptor!.Name = "Empty";
                if (member.GetType().Name == nameof(PropertyInfo))
                {
                    snoopableObject.Descriptor.Type = DescriptorUtils.MakeGenericTypeName((member as PropertyInfo)!.GetMethod.ReturnType);
                }
                else if (member.GetType().Name == nameof(MethodInfo))
                {
                    snoopableObject.Descriptor.Type = DescriptorUtils.MakeGenericTypeName((member as MethodInfo)!.ReturnType);
                }
            }
            return snoopableObject;
        }
        private SnoopableObject EvaluateValue(object value)
        {
            var snoopableObject = new SnoopableObject(_snoopableObject.Context!, value);
            SnoopUtils.Redirect(snoopableObject);
            RestoreSetDescription(value, snoopableObject);
            return snoopableObject;
        }

        private static string EvaluateName(MemberInfo member, ParameterInfo[] parameters, string parameterDecription = "")
        {
            if (parameters is null || parameters.Length == 0) return member.Name;

            return $"{member.Name} ({string.Join(", ", parameters.Select(info => DescriptorUtils.MakeGenericTypeName(info.ParameterType)))} {parameterDecription})";
        }

        private static MemberAttributes EvaluateAttributes(MemberInfo member)
        {
            return member switch
            {
                MethodInfo info => GetModifiers(MemberAttributes.Method, info.Attributes),
                PropertyInfo info => GetModifiers(MemberAttributes.Property, info.CanRead ? info.GetMethod.Attributes : info.SetMethod.Attributes),
                _ => throw new ArgumentOutOfRangeException(nameof(member))
            };
        }
        private static MemberAttributes GetModifiers(MemberAttributes attributes, MethodAttributes propertyAttributes)
        {
            if ((propertyAttributes & MethodAttributes.Static) != 0) attributes |= MemberAttributes.Static;
            if ((propertyAttributes & MethodAttributes.Private) != 0) attributes |= MemberAttributes.Private;
            return attributes;
        }
        private static void RestoreSetDescription(object value, SnoopableObject snoopableObject)
        {
            if (value is not ResolveSet set) return;
            if (set.Variants.Count == 1) return;

            var type = set.Variants.Peek().Result!.GetType();
            var name = DescriptorUtils.MakeGenericTypeName(type);
            snoopableObject.Descriptor!.Name = name;
            snoopableObject.Descriptor.Type = name;
        }
    }
}
